/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/tree/Layer.h"
#include "alkes/tree/Visual.h"
#include "alkes/tree/Composite.h"
#include "alkes/gfx/IGraphics3D.h"

namespace alkes {


class PointerStack
{
public:
    PointerStack()
        : depth_(0)
    {
    }

    ~PointerStack()
    {
    }

    void push(void* p)
    {
        stack_[depth_] = p;
        ++depth_;
    }

    void* pop()
    {
        --depth_;
        return stack_[depth_];
    }

    bool isEmpty() const
    {
        return 0 == depth_;
    }

private:
    enum {
        DEPTH = 64,
    };
    uint16_t depth_;
    void* stack_[DEPTH];
};

Layer::Layer(Context* context)
: context_(context)
, opacity_(100)
, layer_blend_mode_(AL_LAYER_BLEND_NORMAL)
{
    setDrawable(true);
}

Layer::~Layer()
{
    removeAllScene();
}

Context* Layer::getContext() const
{
    return context_;
}

IGraphics2D* Layer::getIGraphics2D() const
{
    return context_ ? restrict_cast<IGraphics2D*>(context_->getGraphics()) : NULL;
}

IGraphics3D* Layer::getIGraphics3D() const
{
    return context_ ? restrict_cast<IGraphics3D*>(context_->getGraphics()) : NULL;
}

Scene* Layer::registerScene(Scene* scene)
{
    if (scene)
    {
        if (scene_list_.push(scene))
        {
            addChild(scene->getRootNode());
        }
    }

    return scene;
}

void Layer::removeScene(Scene* scene)
{
    removeChild(scene->getRootNode());

    if (scene_list_.remove(scene))
    {
        delete scene;
    }
}

void Layer::removeAllScene()
{
    Scene* scene;

    while (0 != (scene = static_cast<Scene*>(scene_list_.shift())))
    {
        removeChild(scene->getRootNode());
        delete scene;
    }
}

void Layer::setOpacity(int16_t opacity)
{
    setProperty(opacity_, opacity);
}

int16_t Layer::getOpacity() const
{
    return opacity_;
}

void Layer::setLayerBlendMode(LayerBlendMode mode)
{
    setProperty(layer_blend_mode_, mode);
}

LayerBlendMode Layer::getLayerBlendMode() const
{
    return layer_blend_mode_;
}

UpdateContext* Layer::getUpdateContext() const
{
    return context_->getUpdateContext();
}

GraphicsContext* Layer::getGraphicsContext() const
{
    return context_->getGraphicsContext();
}

bool Layer::update()
{
    if (!getUpdateContext())
        return false;

    return updateChildren();
}

bool Layer::updateChildren()
{
    UpdateContext* uc = getUpdateContext();

    bool dirty_child = false;
    PointerStack pre_processed;
    PointerStack wait_processing;
    Element* element;
    Deque::Iterator iter = 0;
    int32_t depth = -1;
    uint32_t bits_array[8] = {0,0,0,0,0,0,0,0};

    wait_processing.push(this);

    while (!pre_processed.isEmpty() || !wait_processing.isEmpty())
    {
        if (!wait_processing.isEmpty())
        {
            element = static_cast<Element*>(wait_processing.pop());
            if (element->isComposite())
            {
                if (static_cast<Composite*>(element)->preUpdate(*uc, iter))
                {
                    if (iter)
                    {
                        // push children in reverse.
                        while (iter)
                        {
                            wait_processing.push(*iter);
                            iter = iter->getPrev();
                        }
                        pre_processed.push(element);
                        ++depth;
                        bits_array[depth >> 5] &= ~(1 << (depth & 0x1F));
                    }
                    else
                    {
                        static_cast<Composite*>(element)->postUpdate(*uc, false);
                    }
                }
            }
            else
            {
                dirty_child = static_cast<Visual*>(element)->update(*uc);
                if (dirty_child)
                    bits_array[depth >> 5] |= 1 << (depth & 0x1F);
            }
        }
        else
        {
            dirty_child = static_cast<Composite*>(pre_processed.pop())->postUpdate(
                *uc,
                0 != (bits_array[depth >> 5] & (1 << (depth & 0x1F))));
            --depth;
            if (dirty_child)
                bits_array[depth >> 5] |= 1 << (depth & 0x1F);
        }
    }

    return uc->hasDirtyRect();
}

void Layer::render()
{
    GraphicsContext* gc = getGraphicsContext();
    UpdateContext* uc = getUpdateContext();
    if (!gc || !uc)
    {
        AL_ASSERT(false);
        return ;
    }

    uint16_t dirty_count = 0;
    const Rect* rects = uc->getMergedRects(dirty_count);

    if (!rects)
        return ;

    int32_t i;

    //AL_TRACE();

    if (onBeginRender(*gc))
    {
        for (i = 0; i < dirty_count; ++i)
        {
            // Set default scissor rect
            gc->pushGlobalScissor(rects[i]);

            renderChildren();

            // restore
            gc->popScissorRect();
        }

        onEndRender(*gc);
    }
}

void Layer::renderChildren()
{
    GraphicsContext* gc = getGraphicsContext();

    PointerStack pre_processed;
    PointerStack wait_processing;
    Element* element;
    Deque::Iterator iter = 0;

    wait_processing.push(this);

    while (!pre_processed.isEmpty() || !wait_processing.isEmpty())
    {
        if (!wait_processing.isEmpty())
        {
            element = static_cast<Element*>(wait_processing.pop());
            if (element->isComposite())
            {
                if (static_cast<Composite*>(element)->preRender(*gc, iter))
                {
                    if (iter)
                    {
                        // push children in reverse.
                        do
                        {
                            wait_processing.push(*iter);
                        } while (0 != (iter = iter->getPrev()));

                        pre_processed.push(element);
                    }
                    else
                    {
                        static_cast<Composite*>(element)->postRender(*gc, false);
                    }
                }
            }
            else
            {
                static_cast<Visual*>(element)->render(*gc);
            }
        }
        else
        {
            static_cast<Composite*>(pre_processed.pop())->postRender(*gc, true);
        }
    }
}

bool Layer::onPreRender(GraphicsContext& gc, Iterator& reverse_iter)
{
    IRenderTarget2D* target = gc.getRenderTarget2D();
    if (!target)
        return false; // no target

    bool need_to_render = Clippable::onPreRender(gc, reverse_iter);

    if (need_to_render)
    {
        opacity_backup_ = gc.getOpacity();

        gc.setOpacity(opacity_);

        setAlphaBlendMode(target);
    }

    return need_to_render;
}

void Layer::onPostRender(GraphicsContext& gc, bool rendered_child)
{
    Clippable::onPostRender(gc, rendered_child);

    gc.setOpacity(opacity_backup_);
}

bool Layer::onBeginRender(GraphicsContext& gc)
{
    gc.getRenderTarget2D()->beginDraw2D();
    return true;
}

void Layer::onEndRender(GraphicsContext& gc)
{
    gc.getRenderTarget2D()->endDraw2D();
}


static struct {
    AlphaBlendMode src_mode;
    AlphaBlendMode dst_mode;
} s_blend_table[] = {
    {AL_BLEND_ONE,          AL_BLEND_ZERO},         // AL_LAYER_BLEND_OPACITY
    {AL_BLEND_SRCALPHA,     AL_BLEND_INVSRCALPHA},  // AL_LAYER_BLEND_NORMAL
    {AL_BLEND_DSTCOLOR,     AL_BLEND_ZERO},         // AL_LAYER_BLEND_MULTIPLY
    {AL_BLEND_ONE,          AL_BLEND_ONE},          // AL_LAYER_BLEND_SCREEN
};

void Layer::setAlphaBlendMode(IRenderTarget2D* target)
{
    if (layer_blend_mode_)
    {
        target->setState(AL_GS_ALPHA_BLEND, true);
        target->setAlphaBlendMode(s_blend_table[layer_blend_mode_].src_mode, s_blend_table[layer_blend_mode_].dst_mode);
    }
    else
    {
        target->setState(AL_GS_ALPHA_BLEND, false);
    }
}

}
